package info.batcloud.fanli.core.service.impl;

import com.ctospace.archit.common.pagination.Paging;
import com.jd.open.api.sdk.JdException;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import info.batcloud.fanli.core.constants.CacheNameConstants;
import info.batcloud.fanli.core.constants.ExceptionKeyConstants;
import info.batcloud.fanli.core.constants.MessageKeyConstants;
import info.batcloud.fanli.core.context.StaticContext;
import info.batcloud.fanli.core.domain.DefaultUserDetails;
import info.batcloud.fanli.core.domain.Result;
import info.batcloud.fanli.core.dto.UserDTO;
import info.batcloud.fanli.core.entity.Region;
import info.batcloud.fanli.core.entity.TaobaoOauth;
import info.batcloud.fanli.core.entity.TaobaoPid;
import info.batcloud.fanli.core.entity.User;
import info.batcloud.fanli.core.enums.Gender;
import info.batcloud.fanli.core.enums.TimeUnit;
import info.batcloud.fanli.core.enums.UserLevel;
import info.batcloud.fanli.core.enums.WalletFlowDetailType;
import info.batcloud.fanli.core.exception.BizException;
import info.batcloud.fanli.core.helper.PagingHelper;
import info.batcloud.fanli.core.helper.StringHelper;
import info.batcloud.fanli.core.helper.SvgHelper;
import info.batcloud.fanli.core.repository.UserRepository;
import info.batcloud.fanli.core.security.constants.AuthorityConstants;
import info.batcloud.fanli.core.security.helper.GrantedAuthorityHelper;
import info.batcloud.fanli.core.service.*;
import info.batcloud.fanli.core.settings.*;
import info.batcloud.laxiaoke.open.response.jd.union.CreatePromotionSiteBatchRequest;
import info.batcloud.laxiaoke.open.response.jd.union.CreatePromotionSiteBatchResponse;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.CellStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.*;
import java.util.stream.Collectors;

@Service
@CacheConfig(cacheNames = CacheNameConstants.USER)
public class UserServiceImpl implements UserService {

    private Configuration configuration = new Configuration(Configuration.VERSION_2_3_27);

    @Inject
    private UserRepository userRepository;

    @Inject
    private PasswordEncoder passwordEncoder;

    @Inject
    private SystemSettingService systemSettingService;

    @Inject
    private SmsService smsService;

    @Inject
    private WeixinService weixinService;

    @Inject
    private UserService userService;

    @Inject
    private WalletService walletService;

    @Inject
    private TaobaoPidService taobaoPidService;

    @Inject
    private JdUnionApiService jdUnionApiService;

    @Inject
    private UrlMappingService urlMappingService;

    @Inject
    private RegionService regionService;

    @Inject
    private UserLogService userLogService;

    @Inject
    private TbkPrivateService tbkPrivateService;

    @Inject
    private TmpFileService tmpFileService;

    @Inject
    private CommissionOrderService commissionOrderService;

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @PostConstruct
    public void init() {
        configuration.setTemplateLoader(new ClassTemplateLoader(this.getClass(), ""));
    }

    @Override
    public UserDetails findByTaobaoOauth(TaobaoOauth taobaoOauth) {
        User user = userRepository.findOne(taobaoOauth.getUserId());
        return of(user);
    }

    @Override
    public String genInvitationShareSvgXml(String invitationCode, String bgImg) {
        Template template = null;
        try {
            template = configuration.getTemplate("invitation.svg.ftlh");
        } catch (Exception e) {
            e.printStackTrace();
        }
        StringWriter writer = new StringWriter();
        try {
            Map<String, Object> model = new HashMap<>();
            model.put("invitationCode", invitationCode);
            String invitationUrl = urlMappingService.getWeixinInvitationBindUrl(invitationCode);
            model.put("invitationUrl", invitationUrl);
            model.put("qrCodeUrl", urlMappingService.getQrcodeUrl(invitationUrl));
            model.put("bgImg", bgImg);
            template.process(model, writer);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return writer.toString();
    }

    @Override
    public void genInvitationShareSvg(String invitationCode, String bgImg, OutputStream os) {
        String svg = this.genInvitationShareSvgXml(invitationCode, bgImg);
        try {
            SvgHelper.convertToJpg(svg, os, 1080f, 1920f);
        } catch (TranscoderException e) {
            e.printStackTrace();
        }
    }

    @Override
    @Transactional
    public RegisterResult registerUser(UserRegisterParam param) {
        RegisterResult rs = new RegisterResult();
        //检查phoneCode
        boolean flag = smsService.validatePhoneCode(param.getPhone(), param.getVerifyCode());
        if (!flag) {
            throw new BizException(ExceptionKeyConstants.PHONE_VERIFY_CODE_INVALID);
        }
        if (userRepository.findByPhone(param.getPhone()) != null) {
            rs.setCode(ExceptionKeyConstants.USER_PHONE_EXISTS);
            return rs;
        }
        User user;
        if (StringUtils.isNotBlank(param.getWeixinBindOpenId())) {
            user = userRepository.findByWeixinOpenId(param.getWeixinBindOpenId());
            user.setLocked(false);
        } else {
            user = new User();
            user.setCreateTime(new Date());
            user.setLocked(false);
            user.setLevel(UserLevel.ORDINARY);
        }
        user.setPhone(param.getPhone());
        user.setPassword(passwordEncoder.encode(param.getPassword()));
        this.registerUserSave(rs, user, param.getInvitationCode());
        if (rs.isSuccess()) {
            smsService.unlockPhone(param.getPhone());
        }
        return rs;
    }

    @Override
    public RegisterResult registerUser(WeixinUserRegisterParam param) {
        RegisterResult rs = new RegisterResult();
        User user = new User();
        user.setCreateTime(new Date());
        user.setWeixinUnionId(param.getUnionId());
        user.setLocked(false);
        user.setLevel(UserLevel.ORDINARY);
        user.setWeixinOpenId(param.getWeixinBindOpenId());
        this.registerUserSave(rs, user, param.getInvitationCode());
        return rs;
    }

    private static String[] TITLE_LIST = new String[]{
            "ID",
            "用户昵称",
            "手机号",
            "省份",
            "城市",
            "区县",
            "关系链",
            "会员级别",
            "锁定状态",
            "创建时间",
            "活跃时间"
    };

    @Override
    public File exportUser(ExportParam param) throws IOException {
        File excelFile = tmpFileService.createFile("用户_" + System.currentTimeMillis() + ".xls");
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        sheet.autoSizeColumn(1, true);
        HSSFFont boldFond = workbook.createFont();
        boldFond.setBold(true);
        boldFond.setFontHeightInPoints((short) 16);
        CellStyle foldStyle = workbook.createCellStyle();
        CellStyle commonStyle = workbook.createCellStyle();
        HSSFFont commonFont = workbook.createFont();
        commonFont.setFontHeightInPoints((short) 14);
        commonStyle.setFont(commonFont);
        foldStyle.setFont(boldFond);
        HSSFRow titleRow = sheet.createRow(0);
        for (int i = 0; i < TITLE_LIST.length; i++) {
            HSSFCell titleCell = titleRow.createCell(i);
            //给单元格设置内容
            titleCell.setCellValue(TITLE_LIST[i]);
            titleCell.setCellStyle(foldStyle);
        }
        int page = 1;
        int pageSize = param.getMaxCount() < 100 ? param.getMaxCount() : 100;
        int maxPage = (param.getMaxCount() + pageSize - 1) / pageSize;
        int rowIndex = 1;
        param.setPageSize(pageSize);
        while (true) {
            param.setPage(page);
            Paging<UserDTO> paging = this.search(param);
            for (UserDTO dto : paging.getResults()) {
                HSSFRow row = sheet.createRow(rowIndex++);
                row.setHeightInPoints(20);
                for (int i = 0; i < TITLE_LIST.length; i++) {
                    HSSFCell rowCell = row.createCell(i);
                    rowCell.setCellStyle(commonStyle);
                    switch (TITLE_LIST[i]) {
                        case "ID":
                            rowCell.setCellValue(dto.getId());
                            break;
                        case "用户昵称":
                            rowCell.setCellValue(dto.getNickname());
                            break;
                        case "手机号":
                            rowCell.setCellValue(StringHelper.protect(dto.getPhone()));
                            break;
                        case "省份":
                            if (dto.getProvinceId() != null) {
                                Region province = regionService.findById(dto.getProvinceId());
                                rowCell.setCellValue(province.getName());
                            }
                            break;
                        case "城市":
                            if (dto.getCityId() != null) {
                                Region city = regionService.findById(dto.getCityId());
                                rowCell.setCellValue(city.getName());
                            }
                            break;
                        case "区县":
                            if (dto.getDistrictId() != null) {
                                Region district = regionService.findById(dto.getDistrictId());
                                rowCell.setCellValue(district.getName());
                            }
                            break;
                        case "关系链":
                            rowCell.setCellValue(dto.getRelationPath());
                            break;
                        case "会员级别":
                            rowCell.setCellValue(dto.getLevelTitle());
                            break;
                        case "锁定状态":
                            rowCell.setCellValue(dto.isLocked() ? "已锁定" : "正常");
                            break;
                        case "创建时间":
                            rowCell.setCellValue(DateFormatUtils.format(dto.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
                            break;
                        case "活跃时间":
                            if (dto.getLastActiveTime() != null) {
                                rowCell.setCellValue(DateFormatUtils.format(dto.getLastActiveTime(), "yyyy-MM-dd HH:mm:ss"));
                            }
                            break;
                    }
                }
            }
            if (!paging.getHasNext()) {
                break;
            }
            if (page >= maxPage) {
                break;
            }
            page++;
        }
        for (int i = 0; i < TITLE_LIST.length; i++) {
            sheet.autoSizeColumn(i);
        }
        workbook.write(excelFile);
        return excelFile;
    }

    @Override
    public String findTaobaoSpecialId(long userId) {
        return userRepository.findOne(userId).getTaobaoSpecialId();
    }

    @Override
    public String findOrGenerateTaobaoSpecialId(long userId) {
        String specialId = userService.findTaobaoSpecialId(userId);
        if (specialId != null) {
            return specialId;
        }
        return tbkPrivateService.generateUserSpecialId(userId);
    }


    @Override
    public String findTaobaoRelationId(long userId) {
        return userRepository.findOne(userId).getTaobaoRelationId();
    }

    @Override
    public String findOrGenerateTaobaoRelationId(long userId) {
        String relationId = userService.findTaobaoRelationId(userId);
        if (relationId != null) {
            return relationId;
        }
        return tbkPrivateService.generateUserRelationId(userId);
    }

    @Override
    public void clearTaobaoRelationId(long userId) {
        User user = userRepository.findOne(userId);
        user.setTaobaoRelationId(null);
        userRepository.save(user);
    }

    @Override
    public void clearTaobaoSpecialId(long userId) {
        User user = userRepository.findOne(userId);
        user.setTaobaoSpecialId(null);
        userRepository.save(user);
    }

    private void registerUserSave(RegisterResult rs, User user, String invitationCode) {
        //检查phoneCode
        RegisterSetting registerSetting = systemSettingService.findActiveSetting(RegisterSetting.class);
        User superUser = null;
        if (registerSetting.isUseInvitationCheck()) {
            if (StringUtils.isBlank(invitationCode)) {
                rs.setCode(ExceptionKeyConstants.INVITATION_CODE_NOT_EXISTS);
                return;
            }
        }
        if (StringUtils.isNotBlank(invitationCode)) {
            if (!isInvitationCodeExists(invitationCode)
                    && !invitationCode.equals(registerSetting.getDefaultInvitationCode())) {
                rs.setCode(ExceptionKeyConstants.INVITATION_CODE_NOT_EXISTS);
                return;
            }
            superUser = userRepository.findByInvitationCode(invitationCode);
            if (superUser == null) {
                superUser = userRepository.findByPhone(invitationCode);
            }
            user.setSuperUser(superUser);
            if (superUser != null) {
                //邀请积分
                IntegralSetting integralSetting = systemSettingService.findActiveSetting(IntegralSetting.class);
                if (integralSetting.getInviteEachUserIntegral() > 0) {
                    walletService.addIntegral(superUser.getId(), integralSetting.getInviteEachUserIntegral(), WalletFlowDetailType.USER_INVITATION, null);
                }
            }
        }
        userRepository.save(user);
        if (StringUtils.isNotBlank(invitationCode)) {
            if (superUser != null) {
                user.setRelationPath(superUser.getRelationPath() + user.getId() + "/");
                user.setRelationDepth(superUser.getRelationDepth() + 1);
            } else {
                user.setRelationPath(user.getId() + "/");
                user.setRelationDepth(1);
            }
        } else {
            user.setRelationPath(user.getId() + "/");
            user.setRelationDepth(1);
        }
        user.setInvitationCode(genInvitationCode());
        userRepository.save(user);
        rs.setUserId(user.getId());
        rs.setSuccess(true);
        /**
         * 检查并进行会员直升
         * */
        this.checkUserDirectlyUpgrade(user.getId());
    }

    @Override
    public Result findPassword(UserFindPwdParam param) {
        Result rs = new Result();
        //检查phoneCode
        boolean flag = smsService.validatePhoneCode(param.getPhone(), param.getVerifyCode());
        if (!flag) {
            throw new BizException(ExceptionKeyConstants.PHONE_VERIFY_CODE_INVALID);
        }
        User user = userRepository.findByPhone(param.getPhone());
        if (user == null) {
            rs.setCode(MessageKeyConstants.USER_NOT_EXISTS);
            return rs;
        }
        user.setPassword(passwordEncoder.encode(param.getPassword()));
        userRepository.save(user);
        rs.setSuccess(true);
        smsService.unlockPhone(param.getPhone());
        return rs;
    }

    @Override
    @Cacheable(key = "'OPEN_ID_' + #userId")
    public String getUserOpenId(long userId) {
        User user = userRepository.findOne(userId);
        if (StringUtils.isEmpty(user.getOpenId())) {
            user.setOpenId(UUID.randomUUID().toString());
            userRepository.save(user);
        }
        return user.getOpenId();
    }

    @Override
    @Cacheable(key = "'USER_ID_BY_OPEN_ID_' + #openId")
    public Long getUserIdByOpenId(String openId) {
        User user = userRepository.findByOpenId(openId);
        return user == null ? null : user.getId();
    }

    @Override
    public Result modifyPassword(ModifyPwdParam pwdParam) {
        User user = userRepository.findOne(pwdParam.getUserId());
        Result rs = new Result();
        if (!passwordEncoder.matches(pwdParam.getOldPassword(), user.getPassword())) {
            rs.setCode(MessageKeyConstants.OLD_PASSWORD_INCORRECT);
        } else {
            user.setPassword(passwordEncoder.encode(pwdParam.getPassword()));
            rs.setSuccess(true);
            userRepository.save(user);
        }
        return rs;
    }

    @Override
    public String findUserInvitationCode(long userId) {
        User user = userRepository.findOne(userId);
        return user.getInvitationCode();
    }

    @Override
    public UserUpgradeSetting.Upgrade findUpgradeByUserLevel(UserLevel userLevel) {
        UserUpgradeSetting userUpgradeSetting
                = systemSettingService.findActiveSetting(UserUpgradeSetting.class);
        switch (userLevel) {
            case PLUS:
                return userUpgradeSetting.getPlus();
            case CARRIER:
                return userUpgradeSetting.getCarrier();
            case CHIEF:
                return userUpgradeSetting.getChief();
        }
        return null;
    }

    @Override
    public String genInvitationCode() {
        String code = RandomStringUtils.random(8, false, true);
        if (!isInvitationCodeExists(code)) {
            return code;
        }
        return genInvitationCode();
    }

    @Override
    public void lockUser(long id, boolean lock) {
        User user = userRepository.findOne(id);
        user.setLocked(lock);
        userRepository.save(user);
    }

    @Override
    public void deleteUser(long id) {
        User user = userRepository.findOne(id);
        user.setDeleted(true);
        userRepository.save(user);
    }

    @Override
    public UserDTO findById(long id) {
        User user = userRepository.findOne(id);
        if (user == null) {
            return null;
        }
        return toBO(user);
    }

    @Override
    public ProfileIntegrity checkUserProfileIntegrity(long userId) {
        User user = userRepository.findOne(userId);
        ProfileIntegrity integrity = new ProfileIntegrity();
        integrity.setPhone(StringUtils.isNotBlank(user.getPhone()));
        integrity.setRegion(user.getDistrictId() != null);
        integrity.setWeixin(StringUtils.isNotBlank(user.getWeixinOpenId()));
        integrity.setSuperUser(user.getSuperUser() != null);
        return integrity;
    }

    @Override
    @Transactional
    public void trace(long userId, LoginTraceInfo traceInfo) {
        UserLogService.LogParam logParam = new UserLogService.LogParam();
        logParam.setUserId(userId);
        logParam.setType(traceInfo.getType());
        logParam.setIp(traceInfo.getIp());
        UserLogService.LogResult logResult = userLogService.log(logParam);
        User user = userRepository.findOne(userId);
        switch (traceInfo.getType()) {
            case ACTIVE:
                user.setLastActiveTime(logResult.getLastLogTime());
                user.setActiveTimes(logResult.getTotalTimes());
                user.setActiveIp(traceInfo.getIp());
                break;
            case LOGIN:
                user.setLoginTime(logResult.getLastLogTime());
                user.setLoginTimes(logResult.getTotalTimes());
                user.setLoginIp(traceInfo.getIp());
                break;
        }
        userRepository.save(user);
    }

    @Override
    public UserDTO findByPhone(String phone) {
        User user = userRepository.findByPhone(phone);
        if (user == null) {
            return null;
        }
        return toBO(user);
    }

    private UserDTO toBO(User user) {
        UserDTO dto = new UserDTO();
        BeanUtils.copyProperties(user, dto);
        return dto;
    }

    @Override
    public boolean isUserLevel(long userId, UserLevel userLevel) {
        return userService.findUserLevelByUserId(userId).level >= userLevel.level;
    }

    @Override
    public UserDTO setUserDistrict(long userId, long districtId) {
        Region district = regionService.findById(districtId);
        Region city = regionService.findById(district.getParentId());
        User user = userRepository.findOne(userId);
        Region province = regionService.findById(city.getParentId());
        user.setProvince(province.getName());
        user.setProvinceId(province.getId());
        user.setCityId(city.getId());
        user.setCity(city.getName());
        user.setDistrictId(district.getId());
        user.setDistrict(district.getName());
        userRepository.save(user);
        return toBO(user);
    }

    @Override
    public String getTaobaoPidByUserId(long userId) {
        TaobaoUnionSetting setting = systemSettingService.findActiveSetting(TaobaoUnionSetting.class);
        return setting.getRebatePid();
    }

    @Override
    public Long getTaobaoAdzoneIdByUserId(long userId) {
        User user = userRepository.findOne(userId);
        if (StringUtils.isNotBlank(user.getTaobaoPid())) {
            return user.getTaobaoAdzoneId();
        }
        TaobaoPid pid = fetchUserTaobaoPid(user);
        if (pid != null) {
            return pid.getAdzoneId();
        }
        return null;
    }

    private TaobaoPid fetchUserTaobaoPid(User user) {
        //从PID池中取PID
        TaobaoPid pid = taobaoPidService.fetch();
        if (pid != null) {
            user.setTaobaoPid(pid.getValue());
            user.setTaobaoAdzoneId(pid.getAdzoneId());
            userRepository.save(user);
            return pid;
        }
        return null;
    }

    @Override
    public long getJdPositionIdByUserId(long userId) {
        User user = userRepository.findOne(userId);
        if (user.getJdPositionId() != null) {
            return user.getJdPositionId();
        }
        CreatePromotionSiteBatchRequest request = new CreatePromotionSiteBatchRequest();
        request.setSpaceName("USER_" + user.getId());
        try {
            CreatePromotionSiteBatchResponse result = jdUnionApiService.createPromotionSiteBatch(request);
            user.setJdPositionId(result.getData().getResults().get(0).getId());
            userRepository.save(user);
            return user.getJdPositionId();
        } catch (JdException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public String getPddPidByUserId(long userId) {
        return "";
    }

    @Override
    @Cacheable(key = "'USER_LEVEL_' + #userId")
    public UserLevel findUserLevelByUserId(long userId) {
        User user = userRepository.findOne(userId);
        return user.getLevel();
    }

    @Override
    @CacheEvict(key = "'USER_LEVEL_' + #userId")
    public void setUserLevel(long userId, UserLevel level) {
        User user = userRepository.findOne(userId);
        user.setLevel(level);
        user.setLevelChangeTime(new Date());
        userRepository.save(user);
    }

    @Override
    public boolean isInvitationCodeExists(String invitationCode) {
        RegisterSetting setting = systemSettingService.findActiveSetting(RegisterSetting.class);
        if (invitationCode.equals(setting.getDefaultInvitationCode())) {
            return true;
        }
        return userRepository.countByInvitationCode(invitationCode) > 0
                || userService.findByPhone(invitationCode) != null;
    }

    @Override
    public boolean updateUserField(FieldUpdateParam param) {
        User user = userRepository.findOne(param.getUserId());
        switch (param.getField()) {
            case "nickname":
                user.setNickname(param.getValue());
                break;
            case "city":
                user.setCity(param.getValue());
                break;
            case "avatarUrl":
                user.setAvatarUrl(param.getValue());
                break;
            case "gender":
                user.setGender(Gender.valueOf(param.getValue()));
                break;
        }
        userRepository.save(user);
        return false;
    }

    @Override
    public Result bindPhone(BindPhoneParam param) {
        Result rs = new Result();
        int i = userRepository.countByPhone(param.getPhone());
        if (i > 0) {
            throw new BizException(ExceptionKeyConstants.USER_PHONE_EXISTS);
        }
        //检查phoneCode
        boolean flag = smsService.validatePhoneCode(param.getPhone(), param.getVerifyCode());
        if (!flag) {
            throw new BizException(ExceptionKeyConstants.PHONE_VERIFY_CODE_INVALID);
        }
        User user = userRepository.findOne(param.getUserId());
        if (user == null) {
            rs.setCode(MessageKeyConstants.USER_NOT_EXISTS);
            return rs;
        }
        user.setPhone(param.getPhone());
        userRepository.save(user);
        rs.setSuccess(true);
        smsService.unlockPhone(param.getPhone());
        return rs;
    }

    @Override
    @Transactional
    public CheckUserDirectlyUpgradeResult checkUserDirectlyUpgrade(long userId) {
        UserUpgradeSetting userUpgradeSetting = systemSettingService.findActiveSetting(UserUpgradeSetting.class);
        CheckUserDirectlyUpgradeResult result = new CheckUserDirectlyUpgradeResult();
        if (!userUpgradeSetting.isOpenDirectlyUpgrade()) {
            result.setSuccess(false);
            return result;
        }
        UserLevel userLevel = userService.findUserLevelByUserId(userId);
        if (userLevel.level >= userUpgradeSetting.getDirectlyUpgradeLevel().level) {
            result.setSuccess(false);
            return result;
        }
        userService.setUserLevel(userId, userUpgradeSetting.getDirectlyUpgradeLevel());
        result.setSuccess(true);
        result.setUserLevel(userUpgradeSetting.getDirectlyUpgradeLevel());
        return result;
    }

    @Override
    public int countUserBySuperUserId(long userId) {
        return this.userRepository.countBySuperUserId(userId);
    }

    @Override
    public String determineUserAvatar(String avatarUrl) {
        BaseSetting baseSetting = systemSettingService.findActiveSetting(BaseSetting.class);
        return StringUtils.defaultIfEmpty(avatarUrl, baseSetting.getDefaultUserAvatarUrl());
    }

    @Override
    public String determineUserNickname(String nickname) {
        BaseSetting baseSetting = systemSettingService.findActiveSetting(BaseSetting.class);
        return StringUtils.defaultIfEmpty(nickname, baseSetting.getDefaultUserNickname());
    }


    @Override
    public List<Long> filterUserLevelUserIds(List<Long> relationPathList, UserLevel userLevel) {
        return relationPathList.stream().filter(id -> userService.findUserLevelByUserId(id).level >= userLevel.level).collect(Collectors.toList());
    }

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByPhoneAndDeleted(username, false);
        if (user == null) {
            throw new UsernameNotFoundException(StaticContext.messageSource
                    .getMessage(ExceptionKeyConstants.LOGIN_FAIL, null, "", null));
        }
        return of(user);
    }

    @Override
    @Transactional
    public DefaultUserDetails loadUserByWeixinOpenId(String weixinOpenId) {
        User user = userRepository.findByWeixinOpenIdAndDeleted(weixinOpenId, false);
        return of(user);
    }

    @Override
    @Transactional
    public DefaultUserDetails loadUserByWeixinUnionId(String weixinUnionId) {
        User user = userRepository.findByWeixinUnionIdAndDeleted(weixinUnionId, false);
        return of(user);
    }

    @Override
    @Transactional
    public DefaultUserDetails loadUserByUserId(long userId) {
        return of(userRepository.findOne(userId));
    }

    @Override
    @Transactional
    public boolean bindWeixin(long userId, String weixinCode) {
        WeixinSetting weixinSetting = systemSettingService.findActiveSetting(WeixinSetting.class);
        WeixinService.AccessToken accessToken = weixinService.auth(weixinCode, weixinSetting.getAppId(), weixinSetting.getSecret());
        if (accessToken.getOpenid() == null) {
            return false;
        }
        String openId = accessToken.getOpenid();

        User user = userRepository.findOne(userId);

        WeixinService.UserInfo ui = weixinService.userInfo(accessToken.getAccessToken(), accessToken.getOpenid());
        if (StringUtils.isBlank(user.getNickname()) || user.getNickname().equals(user.getPhone())) {
            user.setNickname(ui.getNickname());
        }
        if (StringUtils.isBlank(user.getProvince())) {
            user.setProvince(ui.getProvince());
        }
        if (StringUtils.isBlank(user.getCity())) {
            user.setCity(ui.getCity());
        }
        if (StringUtils.isBlank(user.getAvatarUrl())) {
            user.setAvatarUrl(ui.getHeadimgurl());
        }
        if (user.getGender() == null) {
            user.setGender(ui.getSex() == 1 ? Gender.MALE : Gender.FAMALE);
        }
        return bindWeixinOpenId(user, openId);
    }

    @Override
    public boolean bindWeixinOpenId(long userId, String openId) {
        User user = userRepository.findOne(userId);
        return bindWeixinOpenId(user, openId);
    }

    private boolean bindWeixinOpenId(User user, String openId) {
        User weixinUser = userRepository.findByWeixinOpenIdAndDeleted(openId, false);
        if (weixinUser != null && !weixinUser.isLocked()) {
            return false;
        }
        user.setWeixinOpenId(openId);
        if (weixinUser != null) {
            weixinUser.setWeixinOpenId(null);
            weixinUser.setDeleted(true);
            userRepository.save(weixinUser);
        }
        userRepository.save(user);
        return true;
    }

    @Override
    @CacheEvict(key = "'USER_LEVEL_' + #userId")
    public void changeUserLevel(long userId, UserLevel userLevel, int month) {
        User user = userRepository.findOne(userId);
        Date now = new Date();
        if (userLevel == user.getLevel()) {
            user.setLevelExpireTime(DateUtils.addMonths(user.getLevelExpireTime() == null
                    ? now : user.getLevelExpireTime(), month));
        } else {
            user.setLevelExpireTime(DateUtils.addMonths(now, month));
            user.setLevel(userLevel);
            user.setLevelChangeTime(new Date());
        }
        userRepository.save(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserLevelCheckUpgradeResult checkUpgradeUserLevel(long userId) {
        User user = userRepository.findOne(userId);
        UserLevelCheckUpgradeResult checkUpgradeResult = new UserLevelCheckUpgradeResult();
        if (user.getLevel() == UserLevel.PLUS) {
            checkUpgradeResult.setUserLevel(user.getLevel());
            checkUpgradeResult.setSuccess(true);
            return checkUpgradeResult;
        }
        UserLevel nextLevel = user.getLevel().next;
        if (nextLevel == null) {
            checkUpgradeResult.setUserLevel(user.getLevel());
            checkUpgradeResult.setSuccess(true);
            return checkUpgradeResult;
        }
        checkUpgradeResult.setUserLevel(nextLevel);
        UserUpgradeSetting.Upgrade upgrade = this.findUpgradeByUserLevel(nextLevel);
        UserUpgradeSetting userUpgradeSetting = systemSettingService.findActiveSetting(UserUpgradeSetting.class);
        List<UpgradeFee> upgradeFeeList = new ArrayList<>();
        checkUpgradeResult.setUpgradeFeeList(upgradeFeeList);
        switch (nextLevel) {
            case PLUS:
                UserUpgradeSetting.Plus plusSetting = userUpgradeSetting.getPlus();
                /**
                 * 判断直推数量
                 * */
                if (plusSetting.getDirectOrderNum() > 0) {
                    int num = commissionOrderService.countValidCommissionOrderNumByUserId(userId);
                    if (plusSetting.getDirectOrderNum() > num) {
                        checkUpgradeResult.setSuccess(false);
                        checkUpgradeResult.setCode(MessageKeyConstants.UPGRADE_USER_LEVEL_FAIL_BY_DIRECT_ORDER_NUM,
                                new Object[]{plusSetting.getDirectOrderNum() - num});
                        return checkUpgradeResult;
                    }
                }

                int count = userRepository.countBySuperUserId(userId);
                if (count >= plusSetting.getInviteUserNum()) {
                } else {
                    checkUpgradeResult.setSuccess(false);
                    checkUpgradeResult.setCode(MessageKeyConstants.UPGRADE_USER_LEVEL_FAIL_BY_INVITATION,
                            new Object[]{plusSetting.getInviteUserNum() - count});
                    return checkUpgradeResult;
                }
                break;
            case CARRIER:
                int directPlusNum = userRepository.countBySuperUserIdAndLevelIn(userId, UserLevel.PLUS, UserLevel.PLUS.next, UserLevel.PLUS.next.next);
                UserUpgradeSetting.Carrier carrierSetting = userUpgradeSetting.getCarrier();
                if (directPlusNum < carrierSetting.getDirectPlusUserNum()) {
                    checkUpgradeResult.setSuccess(false);
                    checkUpgradeResult.setCode(MessageKeyConstants.UPGRADE_USER_LEVEL_FAIL_BY_CHILDREN_NUM,
                            new Object[]{UserLevel.PLUS.getTitle(),
                                    carrierSetting.getDirectPlusUserNum() - directPlusNum});
                    return checkUpgradeResult;
                }
                int teamPlusNum = userRepository.countByRelationPathStartsWithAndRelationPathNotAndRelationDepthLessThanEqualAndLevelIn(user.getRelationPath(),
                        user.getRelationPath(), user.getRelationDepth() + 3, UserLevel.PLUS, UserLevel.PLUS.next, UserLevel.PLUS.next.next);
                if (teamPlusNum < carrierSetting.getTeamPlusUserNum()) {
                    checkUpgradeResult.setSuccess(false);
                    checkUpgradeResult.setCode(MessageKeyConstants.UPGRADE_USER_LEVEL_FAIL_BY_TEAM_NUM,
                            new Object[]{UserLevel.PLUS.getTitle(), carrierSetting.getTeamPlusUserNum() - teamPlusNum});
                    return checkUpgradeResult;
                }
                break;
            case CHIEF:
                int directCarrierNum = userRepository.countBySuperUserIdAndLevelIn(userId, UserLevel.CARRIER, UserLevel.CARRIER.next);
                UserUpgradeSetting.Chief chiefSetting = userUpgradeSetting.getChief();
                if (directCarrierNum < chiefSetting.getDirectCarrierNum()) {
                    checkUpgradeResult.setSuccess(false);
                    checkUpgradeResult.setCode(MessageKeyConstants.UPGRADE_USER_LEVEL_FAIL_BY_CHILDREN_NUM,
                            new Object[]{UserLevel.CARRIER.getTitle(), chiefSetting.getDirectCarrierNum() - directCarrierNum});
                    return checkUpgradeResult;
                }
                int teamCarrierNum = userRepository.countByRelationPathStartsWithAndRelationPathNotAndRelationDepthLessThanEqualAndLevelIn(user.getRelationPath(),
                        user.getRelationPath()
                        , user.getRelationDepth() + 3, UserLevel.CARRIER, UserLevel.CARRIER.next);
                if (teamCarrierNum < chiefSetting.getTeamCarrierNum()) {
                    checkUpgradeResult.setSuccess(false);
                    checkUpgradeResult.setCode(MessageKeyConstants.UPGRADE_USER_LEVEL_FAIL_BY_TEAM_NUM,
                            new Object[]{UserLevel.CARRIER.getTitle(), chiefSetting.getTeamCarrierNum() - teamCarrierNum});
                    return checkUpgradeResult;
                }
                break;
        }
        if (upgrade.getPricePerMonth() > 0) {
            upgradeFeeList.add(new UpgradeFee(TimeUnit.MONTH, upgrade.getPricePerMonth()));
        }
        if (upgrade.getPricePerYear() > 0) {
            upgradeFeeList.add(new UpgradeFee(TimeUnit.YEAR, upgrade.getPricePerYear()));
        }
        if (upgradeFeeList.size() == 0) {
            if (nextLevel == UserLevel.CHIEF) {
                /**
                 * 不允许自助升级为长老级别，
                 * */
                checkUpgradeResult.setSuccess(false);
                checkUpgradeResult.setMsg("恭喜您，获得咪乐长老的升级资格，请联系您的邀请人或客服进行升级！");
                return checkUpgradeResult;
            }
            //如果不存在支付费用，说明不需要支付，则直接进行升级
            userService.setUserLevel(userId, nextLevel);
        } else {
            checkUpgradeResult.setWaitPay(true);
        }
        checkUpgradeResult.setSuccess(true);
        //触发用户链的升级
        if (user.getSuperUser() != null) {
            checkUpgradeUserLevel(user.getSuperUser().getId());
        }
        return checkUpgradeResult;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changeSuperUser(long userId, Long superUserId) {
        User user = userRepository.findOne(userId);
        if (superUserId == null && user.getSuperUser() == null) {
            return;
        }
        String originStartPath, targetStartPath;
        if (user.getSuperUser() != null) {
            originStartPath = user.getSuperUser().getRelationPath();
        } else {
            originStartPath = "";
        }
        if (superUserId == null) {
            targetStartPath = "";
            user.setSuperUser(null);
        } else {
            User superUser = userRepository.findOne(superUserId);
            targetStartPath = superUser.getRelationPath();
            user.setSuperUser(superUser);
        }
        List<User> children = userRepository.findByRelationPathStartsWith(user.getRelationPath());
        for (User child : children) {
            if (StringUtils.isNotBlank(originStartPath)) {
                child.setRelationPath(child.getRelationPath().replaceFirst(originStartPath, targetStartPath));
            } else {
                child.setRelationPath(targetStartPath + child.getRelationPath());
            }
            child.setRelationDepth(child.getRelationPath().split("/").length);
        }
        user.setRelationDepth(user.getRelationPath().split("/").length);
        userRepository.save(user);
        userRepository.save(children);
    }

    @Override
    public UpgradeFee checkUpgradeFee(UserLevel userLevel, TimeUnit timeUnit) {
        UpgradeFee upgradeFee = new UpgradeFee();
        upgradeFee.setTimeUnit(timeUnit);
        UserUpgradeSetting userUpgradeSetting = systemSettingService.findActiveSetting(UserUpgradeSetting.class);
        switch (userLevel) {
            case PLUS:
                upgradeFee.setPrice(userUpgradeSetting.getPlus().getPriceByTimeUnit(timeUnit));
                break;
            case CARRIER:
                upgradeFee.setPrice(userUpgradeSetting.getCarrier().getPriceByTimeUnit(timeUnit));
                break;
            case CHIEF:
                upgradeFee.setPrice(userUpgradeSetting.getChief().getPriceByTimeUnit(timeUnit));
                break;
        }
        return upgradeFee;
    }

    @Override
    public Paging<UserDTO> search(SearchParam param) {
        Specification<User> specification = (root, query, cb) -> {
            Predicate predicate = cb.conjunction();
            List<Expression<Boolean>> expressions = predicate.getExpressions();
            if (StringUtils.isNotBlank(param.getPhone())) {
                expressions.add(cb.equal(root.get("phone"), param.getPhone()));
            }
            if (param.getId() != null) {
                expressions.add(cb.equal(root.get("id"), param.getId()));
            }
            if (param.getCityId() != null) {
                expressions.add(cb.equal(root.get("cityId"), param.getCityId()));
            }
            if (param.getDistrictId() != null) {
                expressions.add(cb.equal(root.get("districtId"), param.getDistrictId()));
            }
            if (param.getProvinceId() != null) {
                expressions.add(cb.equal(root.get("provinceId"), param.getProvinceId()));
            }
            if (param.getLevel() != null) {
                expressions.add(cb.equal(root.get("level"), param.getLevel()));
            }
            expressions.add(cb.equal(root.get("deleted"), false));
            if (StringUtils.isNotBlank(param.getRelationPath())) {
                List<Long> ids = Arrays.asList(param.getRelationPath().split("/")).stream().map(o -> Long.valueOf(o)).collect(Collectors.toList());
                expressions.add(cb.in(root.get("id")).value(ids));
            }
            if (StringUtils.isNotBlank(param.getStartRelationPath())) {
                expressions.add(cb.like(root.get("relationPath"), param.getStartRelationPath() + "%"));
            }
            if (param.getUserLevelValid() != null) {
                if (param.getUserLevelValid()) {
                    expressions.add(cb.lessThanOrEqualTo(root.get("levelExpireTime"), new Date()));
                } else {
                    expressions.add(cb.greaterThanOrEqualTo(root.get("levelExpireTime"), new Date()));
                }

            }
            if (param.getLocked() != null) {
                expressions.add(cb.equal(root.get("locked"), param.getLocked()));
            }
            return predicate;
        };
        Sort sort = null;
        if (param.getSort() != null) {
            switch (param.getSort()) {
                case ID_ASC:
                    sort = new Sort(Sort.Direction.ASC, "id");
                    break;
                case ACTIVE_TIME_DESC:
                    sort = new Sort(Sort.Direction.ASC, "lastActiveTime");
                    break;
                case ID_DESC:
                    sort = new Sort(Sort.Direction.DESC, "id");
                    break;
                case LOGIN_TIME_ASC:
                    sort = new Sort(Sort.Direction.ASC, "loginTime");
                    break;
                case LOGIN_TIME_DESC:
                    sort = new Sort(Sort.Direction.DESC, "loginTime");
                    break;
                case LOGIN_TIMES_ASC:
                    sort = new Sort(Sort.Direction.ASC, "loginTimes");
                    break;
                case LOGIN_TIMES_DESC:
                    sort = new Sort(Sort.Direction.DESC, "loginTimes");
                    break;
                case RELATION:
                    sort = new Sort(Sort.Direction.ASC, "relationPath");
                    break;

            }
        } else {
            sort = new Sort(Sort.Direction.DESC, "id");
        }
        Pageable pageable = new PageRequest(param.getPage() - 1,
                param.getPageSize(), sort);
        Page<User> page = userRepository.findAll(specification, pageable);
        Paging<UserDTO> paging = PagingHelper.of(page, item -> toBO(item), param.getPage(), param.getPageSize());
        if (page.getTotalElements() > 0 && StringUtils.isNotBlank(param.getRelationPath())) {
            List<String> userIds = Arrays.asList(param.getRelationPath().split("/"));
            List<UserDTO> list = new ArrayList<>();
            list.addAll(paging.getResults());
            Collections.sort(list, Comparator.comparingInt(o -> userIds.indexOf(o.getId() + "")));
            paging.setResults(list);
        }
        return paging;
    }

    private DefaultUserDetails of(User user) {
        if (user == null) {
            return null;
        }
        DefaultUserDetails userDetails = new DefaultUserDetails();
        userDetails.setPassword(user.getPassword());
        userDetails.setUsername(user.getPhone());
        userDetails.setAccountNonExpired(true);
        userDetails.setAccountNonLocked(!user.isLocked());
        userDetails.setCredentialsNonExpired(true);
        userDetails.setEnabled(!user.isLocked());
        userDetails.setUserId(user.getId());
        userDetails.setAvatarUrl(user.getAvatarUrl());
        userDetails.setNickname(user.getNickname());
        if (user.getSuperUser() != null) {
            userDetails.setSuperUserId(user.getSuperUser().getId());
        }
        List<String> authorities = new ArrayList<>();
        authorities.add(AuthorityConstants.USER);
        userDetails.getAuthorities().addAll(GrantedAuthorityHelper.valueOf(authorities));
        return userDetails;
    }


}
